//==============================================================================
//
//            DXML - the XML reader, writer and tree in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2006-07-22 13:28:57 $ $Revision: 1.30 $
//
//==============================================================================

#include "ofc/DXML.h"

#include "ofc/DText.h"

#include <string.h>
#include <stdio.h>

#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DList.h"

#include "ofc/DTextReadable.h"
#include "ofc/DTextWritable.h"

#if defined(HAVE_EXPAT_H) && defined(HAVE_LIBEXPAT)
# define HAVE_DXML (1)
#endif


#ifdef HAVE_DXML

#include "expat.h"

#define DXML_ELEMENT      (1)
#define DXML_ATTRIBUTE    (2)
#define DXML_TEXT         (3)
#define DXML_CDATA        (4)
#define DXML_ENTITY_REF   (5) // Not used
#define DXML_ENTITY       (6) // Not used
#define DXML_PI           (7)
#define DXML_COMMENT      (8)
#define DXML_DOCUMENT     (9)
#define DXML_DOC_TYPE     (10) // Not used
#define DXML_DOC_FRAGMENT (11) // Not used
#define DXML_NOTATION     (12) // Not used
#define DXML_NAMESPACE    (13) // Not in DOM

typedef XML_Char   DXMLChar;

//
// The DXMLHandler protocol specifies the methods a xml handler
// must implement for processing the contents of a xml source.
//

@protocol DXMLHandler

//
// Process the start of a document
// 
// @param version     the version number
// @param encoding    the encoding
// @param standalone  is the xml document standalone ? (-1 = missing)
// 
// @return success
// 
- (BOOL) startDocument :(const DXMLChar *) version :(const DXMLChar *) encoding :(int) standalone;

//
// Process the end of a document
// 
// @return success
// 
- (BOOL) endDocument;

//
// Process the start of an element
// 
// @param name     the name of the element
// 
// @return success
// 
- (BOOL) startElement :(const DXMLChar *) name;

//
// Process an attribute for an element (after a startElement)
// 
// @param attribute  the attribute
// @param value      the value for the attribute
// 
// @return success
// 
- (BOOL) attribute :(const DXMLChar *) attribute :(const DXMLChar *) value;

//
// Process the end of an element
// 
// @return success
//
- (BOOL) endElement;

//
// Process a string of characters
// 
// @param text     the text of the characters
// 
// @return success
// 
- (BOOL) characters :(const DXMLChar *) text;

//
// Process a comment
// 
// @param text     the text of the comment
// 
// @return success
// 
- (BOOL) comment :(const DXMLChar *) text;

//
// Process a processing instruction
// 
// @param target   the target
// @param value    the value
// 
// @return success
// 
- (BOOL) processingInstruction :(const DXMLChar *) target :(const DXMLChar *) value;

//
// Process the start of a CDATA section
// 
// @return success
// 
- (BOOL) startCDATA;

//
// Process the end of a CDATA section
// 
// @return success
// 
- (BOOL) endCDATA;

//
// Start of a namespace declaration
// 
// @param prefix   the prefix
// @param uri      the uri
// 
// @return success
// 
- (BOOL) startNamespace :(const DXMLChar *) prefix :(const DXMLChar *) uri;

//
// End of a namespace declaration
// 
// @return success
// 
- (BOOL) endNamespace;

//
// Process unparsed text in the xml document (e.g. the DTD)
// 
// @param text     the unparsed text
// 
// @return success
// 
- (BOOL) unparsed :(const DXMLChar *) text;

// 
// An error is found during the parsing of the file
// 
// @param number       the error number
// @param name         the name of the config file
// @param lineNumber   the line number in the config file
// @param columnNumber the column number in the config file
// 
// @return success
// 
- (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber;

@end




//
// The DXMLReader class implements methods for parsing a xml file. The class 
// uses expat for the low level parsing. The parsing of DTDs and External 
// Entities are not supported. The method 'characters' returns the exact 
// contents between the start elements and end element, including linefeed 
// and indent spacing. The method 'unparsed' returns everything that could 
// not be parsed, including DTDs and External Entities References. Use a character 
// that can't be part of an URI e.g. '|' for the namespace separator.
//

@interface DXMLReader : Object
{
@private
  id <DXMLHandler>     _handler;     // the reference to the xml handler
  int                  _bufferSize;  // the size of the buffer (def. 8192)
  XML_Parser           _parser;      // the xml parser
  DText               *_name;        // the name of the source
  DXMLChar             _separator;   // the namespace separator (def. \0 = no namespace)
  DXMLChar            *_encoding;    // the encoding to be used (def. NULL = from xml file)
  DText               *_characters;  // the characters buffer
  DText               *_unparsed;    // the unparsed buffer
}
#endif

#endif


#ifdef HAVE_DXML


@implementation DXMLReader


//// Constructors

//
// Initialise a xml reader
//
// @return the object
//

- (DXMLReader *) init
{
  [super init];

  _bufferSize = 8192;
  _separator  = EOS;
  _encoding   = NULL;

  _handler    = nil;
  _characters = [DText new];
  _unparsed   = [DText new];
  _name       = [DText new];
  
  _parser     = NULL;
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
  
  return [super shallowCopy];
}


//// Deconstructor

//
// Free the object
//
// @return the object
//

- free
{
  [_characters free]; _characters = nil;
  [_unparsed   free]; _unparsed   = nil;
  [_name       free]; _name       = nil;
  
  _handler = nil;
    
  if (_encoding != NULL)
  {
    objc_free(_encoding);
  }
  _encoding = NULL;

  if (_parser != NULL)
  {
    XML_ParserFree(_parser); _parser = NULL;
  }
  
  return [super free];
}


//// Member methods

//
// Set the buffer size for the parser
// 
// @param size     the buffer size
// 
// @return the object
// 
- (DXMLReader *) bufferSize :(int) size
{
  if (size <= 0)
  {
    WARNING(DW_INVALID_ARG, "size");
  }
  else
  {
    _bufferSize = size;
  }
  
  return self;
}

//
// Get the buffer size for the parser
// 
// @return the size
// 

- (int) bufferSize
{
  return _bufferSize;
}

//
// Set the encoding overrider 
// 
// @param encoding   the encoding to be used
// 
// @return the object
// 
- (DXMLReader *) encoding :(const DXMLChar *) encoding
{
  if (_encoding != NULL)
    objc_free(_encoding);
  
  if ((encoding == NULL) || (*encoding == EOS))
  {
    _encoding = NULL;
  }
  else
  {
    int length = strlen(encoding);
    
    _encoding = (DXMLChar *) objc_malloc((length+1) * sizeof(DXMLChar));
    
    strcpy(_encoding, encoding);
  }
  
  return self;
}
    
//
// Get the encoding used for the xml documents
// 
// @return the encoding overrider (or NULL)
// 
- (const DXMLChar *) encoding
{
  return _encoding;
}


//// Parser methods


#if _PRIVATE_

//
// Some returning code..
// 

static void emptyCharacters(DText *characters, id <DXMLHandler> handler)
{
  if ([characters length] > 0)
  {
    [handler characters :[characters cstring]];
    
    [characters clear];
  }
}

static void emptyUnparsed  (DText *unparsed,   id <DXMLHandler> handler)
{
  if ([unparsed length] > 0)
  {
    [handler unparsed :[unparsed cstring]];
    
    [unparsed clear];
  }
}

//
// List of wrapper functions that 'connect' expat with objc
// 

static void xmlDeclHandler(void *userData, const XML_Char *version, const XML_Char *encoding, int standalone)
{
  DXMLReader *reader = (DXMLReader *) userData;
  
  [reader->_handler startDocument :version :encoding :standalone];
}

static void startElementHandler(void *userData, const XML_Char *name, const XML_Char **atts)
{
  int i;

  DXMLReader *reader = (DXMLReader *) userData;

  emptyCharacters(reader->_characters, reader->_handler);
  emptyUnparsed  (reader->_unparsed,   reader->_handler);
  
  [reader->_handler startElement :name];
  
  for (i = 0; atts[i] != NULL; i+=2)
  {
    [reader->_handler attribute :atts[i] :atts[i+1]];
  }
}

static void endElementHandler(void *userData, const XML_Char *name)
{
  DXMLReader *reader = (DXMLReader *) userData;

  emptyCharacters(reader->_characters, reader->_handler);
  emptyUnparsed  (reader->_characters, reader->_handler);
  
  [reader->_handler endElement];
}

static void characterDataHandler(void *userData, const XML_Char *s, int len)
{
  int i;
  
  DXMLReader *reader = (DXMLReader *) userData;

  emptyUnparsed(reader->_unparsed,   reader->_handler);
  
  for (i = 0; i < len; i++)
  {
    [reader->_characters push :s[i]];
  }
}

static void commentHandler(void *userData, const XML_Char *data)
{
  DXMLReader *reader = (DXMLReader *) userData;

  emptyCharacters(reader->_characters, reader->_handler);
  emptyUnparsed  (reader->_unparsed,   reader->_handler);

  [reader->_handler comment :data];
}

static void processingInstructionHandler(void *userData, const XML_Char *target, const XML_Char *data)
{
  DXMLReader *reader = (DXMLReader *) userData;

  emptyCharacters(reader->_characters, reader->_handler);
  emptyUnparsed  (reader->_unparsed,   reader->_handler);
  
  [reader->_handler processingInstruction :target :data];
}

static void startCdataSectionHandler(void *userData)
{
  DXMLReader *reader = (DXMLReader *) userData;

  emptyCharacters(reader->_characters, reader->_handler);
  emptyUnparsed  (reader->_unparsed,   reader->_handler);

  [reader->_handler startCDATA];
}

static void endCdataSectionHandler(void *userData)
{
  DXMLReader *reader = (DXMLReader *) userData;

  emptyCharacters(reader->_characters, reader->_handler);
  
  [reader->_handler endCDATA];
}

static void defaultHandler(void *userData, const XML_Char *s, int len)
{
  int i;
  
  DXMLReader *reader = (DXMLReader *) userData;
  
  emptyCharacters(reader->_characters, reader->_handler);
  
  for (i = 0; i < len; i++)
  {
    [reader->_unparsed push :s[i]];
  }
}

static void startNamespaceDeclHandler(void *userData, const XML_Char *prefix, const XML_Char *uri)
{
  DXMLReader *reader = (DXMLReader *) userData;
  
  [reader->_handler startNamespace :prefix :uri];
}

static void endNamespaceDeclHandler(void *userData, const XML_Char *prefix)
{
  DXMLReader *reader = (DXMLReader *) userData;
  
  [reader->_handler endNamespace];
}
#endif

//
// Parse a XML file
// 
// @param source     the source of the xml file
// @param name       the name of the xml file
// @param handler    the xml handler
// @param separator  the namespace separator
// 
// @return success
// 

- (BOOL) parse :(id <DTextReadable>) source :(const char *) name :(id <DXMLHandler>) handler :(DXMLChar) separator
{
  BOOL ok   = NO;

  if (source == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "source");
  }
  else if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if (handler == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "handler");
  }
  else
  {
    BOOL done = NO;

    [_name set :name];
    
    _parser = (separator == EOS) ? XML_ParserCreate(_encoding) : XML_ParserCreateNS(_encoding, separator);
    
    _separator = separator;
    
    XML_SetUserData(_parser, self);
  
    XML_SetXmlDeclHandler(_parser, xmlDeclHandler);
    XML_SetElementHandler(_parser, startElementHandler, endElementHandler);
    XML_SetCharacterDataHandler(_parser, characterDataHandler);
    XML_SetCommentHandler(_parser, commentHandler);
    XML_SetProcessingInstructionHandler(_parser, processingInstructionHandler);
    XML_SetCdataSectionHandler(_parser, startCdataSectionHandler, endCdataSectionHandler);
    XML_SetDefaultHandlerExpand(_parser, defaultHandler);
    XML_SetNamespaceDeclHandler(_parser, startNamespaceDeclHandler, endNamespaceDeclHandler);

    _handler = handler;

    [_characters clear];
    [_unparsed   clear];
    
    ok = YES;
    
    while ((!done) && (ok))
    {
      DText *text = [source readText :(long) _bufferSize];
    
      done = ([text length] < _bufferSize);
    
      if (!XML_Parse(_parser, [text cstring], [text length], done))
      {
        [_handler error :XML_GetErrorCode(_parser) :name :XML_GetCurrentLineNumber(_parser) :XML_GetCurrentColumnNumber(_parser)];
      
        ok = NO;
      }
    
      [text free];
    }
    [_handler endDocument];

    XML_ParserFree(_parser); _parser = NULL;
    
    [_name clear];
    
    _handler = nil;
  }
  
  return ok;
}

//
// Return the current line number during parsing
// 
// @return the current line number (or 0)
// 
- (int) lineNumber
{
  return ((_parser != NULL) ? XML_GetCurrentLineNumber(_parser) : 0);
}

//
// Return the current column number during parsing
// 
// @return the current column number
// 
- (int) columnNumber
{
  return ((_parser != NULL) ? XML_GetCurrentColumnNumber(_parser) : 0);
}

//
// Return the name of the source during parsing
// 
// @return the current source name
// 
- (const char *) name
{
  return [_name cstring];
}

//
// Translate the error to a text string
// 
// @param error    the error number
// 
// @return the text string
// 

+ (const char *) errorToString :(int) error
{
  return (XML_ErrorString(error));
}

@end
#endif



  
#if _INTERFACE_

#ifdef HAVE_DXML

//
// The DXMLNode class implements methods for storing information
// from a XML source in a node. The following scheme is used by
// the class.
// <pre>
//        Type              Name           Value      Remarks
//        ----------------------------------------------------
//        DXML_ELEMENT      Name           NULL
//        DXML_ATTRIBUTE    Name           value
//        DXML_TEXT         #text          text       1)
//        DXML_CDATA        #cdata         text       1)
//        DXML_ENTITY_REF                             Not used
//        DXML_ENTITY                                 Not used
//        DXML_PI           target         value
//        DXML_COMMENT      #comment       text       1)
//        DXML_DOCUMENT     #document      value      1)
//        DXML_DOC_TYPE                               Not used
//        DXML_DOC_FRAGMENT                           Not used
//        DXML_NOTATION                               Not used
//        DXML_NAMESPACE    prefix         uri        2)
//        ----------------------------------------------------
//        1) Name parameter for set::: method is ignored
//        2) Not present in DOM
// </pre>

@interface DXMLNode : Object
{
  int     _type;   // the type of the node DXML_ELEMENT .. DXML_NAMESPACE
  DText  *_name;   // the 'name'of the node 
  DText  *_value;  // the value of the node
}
#endif
#endif


#ifdef HAVE_DXML


@implementation DXMLNode

  
//// Constructors

//
// Initialise an empty xml node
//
// @return the object
//

- (DXMLNode *) init
{
  [super init];

  _type     = DXML_ELEMENT;
  _name     = nil;
  _value    = nil;
  
  return self;
}

//
// Initialise a xml node with a type, a name and a value
// 
// @param type       the xml node type (DXML_..)
// @param name       the name of the node
// @param value      the value of the node (or NULL)
//
// @return the object
//

- (DXMLNode *) init :(int) type :(const char *) name :(const char *) value
{
  [self init];

  [self set :type :name :value];
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object
//
// @return the object
//
- shallowCopy
{
  DXMLNode *copy = [super shallowCopy];
  
  if (_name != nil)
  {
    copy->_name = [_name copy];
  }
  
  if (_value != nil)
  {
    copy->_value = [_value copy];
  }
    
  return copy;
}


//// Deconstructor

//
// Free the object
//
// @return the object
//

- free
{
  if (_name != nil)
  {
    [_name free]; _name = nil;
  }
  
  if (_value != nil)
  {
    [_value free]; _value = nil;
  }
  
  return [super free];
}


//// Member methods

//
// Return the type of the xml node
// 
// @return the type (DXML_ELEMENT..DXML_NOTATION)
// 
- (int) type
{
  return _type;
}

//
// Return the name of the xml node
// 
// @return the name of the node (or NULL)
// 
- (const char *) name
{
  return (_name != nil ? [_name cstring] : NULL);
}

//
// Return the value of the xml node
// 
// @return the value of the node (or NULL)
// 
- (const char *) value
{
  return (_value != nil ? [_value cstring] : NULL);
}


//// Main method

#if _PRIVATE_

static void setNode(DXMLNode *node, int type, const char *name, const char *value, BOOL optName, BOOL reqValue)
{
  if ((!optName) && ((name == NULL) || (*name == EOS)))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if ((reqValue) && (value == NULL))
  {
    WARNING(DW_INVALID_ARG, "value");
  }

  node->_type = type;
  
  if (name != NULL)
  {
    if (node->_name == nil)
    {
      node->_name = [DText new];
    }
    [node->_name set :name];
  }
  else
  {
    if (node->_name != nil)
    {
      [node->_name free];
      
      node->_name = nil;
    }
  }
  
  if (value != NULL)
  {
    if (node->_value == nil)
    {
      node->_value = [DText new];
    }
    [node->_value set :value];
  }
  else
  {
    if (node->_value != nil)
    {
      [node->_value free]; node->_value = nil;
    }
  }
}
#endif

//
// Set the xml node (see table for parameters)
// 
// @param type       the xml node type (DXML_..)
// @param name       the name of the node (or NULL)
// @param value      the value of the node (or NULL)
//
// @return the object
//
- (DXMLNode *) set :(int) type :(const char *) name :(const char *) value
{
  switch (type)
  {
    case DXML_ELEMENT   : setNode(self, type, name,        value, NO,  NO); break;
    case DXML_ATTRIBUTE : setNode(self, type, name,        value, NO,  YES); break;
    case DXML_TEXT      : setNode(self, type, "#text",     value, NO,  YES); break;
    case DXML_CDATA     : setNode(self, type, "#cdata",    value, NO,  YES); break;
    case DXML_PI        : setNode(self, type, name,        value, NO,  YES); break;
    case DXML_COMMENT   : setNode(self, type, "#comment",  value, NO,  YES); break;
    case DXML_DOCUMENT  : setNode(self, type, "#document", value, NO,  YES); break;
    case DXML_NAMESPACE : setNode(self, type, name,        value, YES, YES); break;
    default             : WARNING(DW_INVALID_ARG, "type"); break;
  }
  
  return self;
}

@end
#endif

  
  

#if _INTERFACE_

#ifdef HAVE_DXML

//
// The xml writer class implements methods for writing a xml file. This class 
// does NOT check the correctness of the written xml. The method 'characters' 
// translates the special characters (<,>,&,",'), the method 'unparsed' does 
// not. With the last method an entity or character reference can be written.
// Linefeeds are never added by the writer. Linefeeds must be supplied by the 
// calling software by using 'characters:' or 'unparsed:'. Use a character 
// that can't be part of an URI e.g. '|' for the namespace separator.
//
   
@interface DXMLWriter : Object <DXMLHandler>
{
@private
  id <DTextWritable>  _dest;         // the config file
  DList              *_elements;     // the stack with the element names
  DList              *_namespaces;   // the stack with the namespaces
  DList              *_unprocessed;  // the list with the unprocessed namespaces
  BOOL                _closeElement; // should the last element still be closed ?
  BOOL                _inCData;      // is the writer inside a CDATA section ?
  char                _separator;    // the namespace separator (or EOS for none)
}
#endif
#endif



#ifdef HAVE_DXML

@implementation DXMLWriter

#if _PRIVATE_
  
//
// Local functions
// 
static BOOL writeTranslatedChar(id <DTextWritable> dest, char ch)
{
  BOOL ok = NO;
  
  switch(ch)
  {
    case '<'  : ok = [dest writeText :"&lt;"];   break;
    case '>'  : ok = [dest writeText :"&gt;"];   break;
    case '&'  : ok = [dest writeText :"&amp;"];  break;
    case '\'' : ok = [dest writeText :"&apos;"]; break;
    case '"'  : ok = [dest writeText :"&quot;"]; break;
    default   : ok = [dest writeChar :ch];       break;
  }
  
  return ok;
}

static BOOL closeElement(DXMLWriter *self)
{
  BOOL ok = YES;
  
  if (self->_closeElement)
  {
    ok = [self->_dest writeText :">"];
      
    self->_closeElement = NO;
  }
  
  return ok;
}

static BOOL writeTranslatedName(DXMLWriter *self, const char *name)
{
  BOOL ok   = YES;
  BOOL done = NO;
  
  if (self->_separator != EOS)
  {
    DList *elements = [DList split :name :self->_separator :1];
    
    if ([elements length] == 2)
    {
      DListIterator *iter = [DListIterator alloc];
      DXMLNode      *ns   = nil;
      DText         *uri  = [elements get :0];
      DText         *tag  = [elements get :1];
      
      [iter init :self->_namespaces];
      
      ns = [iter first];
      
      while (ns != nil)
      {
        if ([uri icompare :[ns value]] == 0)
        {
          break;
        }
        
        ns = [iter next];
      }
      
      [iter free];
      
      if (ns != nil) 
      {
        // the prefix
        if ([ns name] != NULL)
        {
          ok &= [self->_dest writeText :[ns name]];
          ok &= [self->_dest writeChar :':'];
        }
      }
      else
      {
        WARNING(DW_UNEXPECTED_ERROR," Unknown uri in name");
      }
      
      ok &= [self->_dest writeText :[tag cstring]];
      
      done = YES;
    }
    
    [elements free];
  }
    
  if (!done)
  {
    ok &= [self->_dest writeText :name];
  }
  
  return ok;
}
#endif

//// Constructors

//
// Initialise a xml writer
//
// @return the object
//

- (DXMLWriter *) init
{
  [super init];

  _dest         = nil;
  _closeElement = NO;  
  _inCData      = NO;
  _elements     = [DList new];
  _namespaces   = [DList new];
  _unprocessed  = [DList new];
  
  return self;
}

//
// Initialise a xml writer with a destination file
// 
// @param destination  the xml file
// @param separator    the namespace separator
//
// @return the object
//

- (DXMLWriter *) init :(id <DTextWritable>) destination :(char) separator 
{
  [self init];

  [self start :destination :separator];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");

  return [super shallowCopy];
}


//// Deconstructor

//
// Free the object
//
// @return the object
//

- free
{
  [_elements    free];
  [_namespaces  free];
  [_unprocessed free];
  
  return [super free];
}


//// Writer methods

//
// Start the writing of a xml file
// 
// @param destination  the xml file
// @param separator    the namespace separator (or EOS for no namespaces)
// 
// @return the object
// 

- (BOOL) start :(id <DTextWritable>) destination :(char) separator
{
  if (destination == nil)
  {
    WARNING(DW_INVALID_ARG, "destination");
  }
  else 
  {
    _dest      = destination;
    _separator = separator;
  }
  
  return (destination != nil);
}

//
// Write the start of a document
// 
// @param version      the version number (or NULL)
// @param encoding     the encoding (or NULL)
// @param standalone   is the xml document standalone (-1 = missing) ?
// 
// @return success
// 
- (BOOL) startDocument :(const DXMLChar *) version :(const DXMLChar *) encoding :(int) standalone
{
  BOOL ok = NO;
  
  if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else
  {
    ok = [_dest writeText :"<?xml"];
    
    if (version != NULL)
    {
      ok &= [_dest writeText :" version=\""];
      ok &= [_dest writeText :version];
      ok &= [_dest writeChar :'"'];
    }
    
    if (encoding != NULL)
    {
      ok &= [_dest writeText :" encoding="];
      ok &= [_dest writeText :encoding];
      ok &= [_dest writeChar :'"'];
    }
  
    if (standalone != -1)
    {
      ok &= [_dest writeText :" standalone="];
    
      if (standalone)
      {
        ok &= [_dest writeText :"\"yes\""];
      }
      else
      {
        ok &= [_dest writeText :"\"no\""];
      }
    }
    
    ok &= [_dest writeText :"?>"];
  }
  
  return (_dest != nil);
}

//
// Write the end of a document
// 
// @return success
// 
- (BOOL) endDocument
{
  BOOL ok = NO;
  
  if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else
  {
    ok = YES;
    
    while ([_elements length] > 0)
    {
      ok &= [self endElement];
    }
  }
  
  return ok;
}

//
// Write the start of an element
// 
// @param name     the name of the element
// 
// @return success
// 
- (BOOL) startElement :(const DXMLChar *) name
{
  BOOL ok = NO;
  
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else
  {
    DXMLNode *ns = [_unprocessed dequeue];
    
    DText *element = [DText alloc];
    
    ok = closeElement(self);
    
    ok &= [_dest writeChar :'<'];
    
    ok &= writeTranslatedName(self, name);

    [_elements push :[element init :name]];

    while (ns != nil)
    {
      ok &= [_dest writeText :" xmlns"];
      
      if ([ns name] != NULL)
      {
        ok &= [_dest writeChar :':'];
        ok &= [_dest writeText :[ns name]];
      }
      
      ok &= [_dest writeChar :'='];
      ok &= [_dest writeChar :'"'];
      ok &= [_dest writeText :[ns value]];
      ok &= [_dest writeChar :'"'];
      
      [ns free];
      
      ns = [_unprocessed dequeue];
    }

    _closeElement = YES;
  }

  return ok;
}

//
// Write an attribute for an element
// 
// @param attribute  the attribute
// @param value      the value for the attribute
// 
// @return success
// 
- (BOOL) attribute :(const DXMLChar *) attribute :(const DXMLChar *) value
{
  BOOL ok = NO;
  
  if ((attribute == NULL) || (*attribute == EOS))
  {
    WARNING(DW_INVALID_ARG, "attribute");
  }
  else if ((value == NULL) || (*value == EOS))
  {
    WARNING(DW_INVALID_ARG, "value");
  }
  else if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else if (!_closeElement)
  {
    WARNING(DW_UNEXPECTED_ERROR, "attribute outside startElement");
  }
  else
  {
    ok  = [_dest writeChar :' '];
    ok &= writeTranslatedName(self, attribute);
    ok &= [_dest writeText :"=\""];
    
    for (; *value != EOS; value++)
    {
      ok &= writeTranslatedChar(_dest, *value);
    }
    ok &= [_dest writeChar :'"'];
  }
  
  return ok;
}

//
// Write the end of an element
// 
// @return success
//
- (BOOL) endElement
{
  BOOL ok = NO;
  
  DText *element = [_elements pop];
  
  if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else if (element == nil)
  {
    WARNING(DW_UNEXPECTED_ERROR, "endElement without startElement");
  }
  else
  {
    ok = closeElement(self);
    
    ok &= [_dest writeText :"</"];
    ok &= writeTranslatedName(self, [element cstring]);
    ok &= [_dest writeChar :'>'];
  }
  
  return ok;
}

//
// Write a string of characters
// 
// @param text     the text of the characters
// 
// @return success
// 
- (BOOL) characters :(const DXMLChar *) text
{
  BOOL ok = NO;
  
  if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else
  {
    ok = closeElement(self);
    
    if (text != NULL)
    {
      for (; *text != EOS; text++)
      {
        if (_inCData)
        {
          ok &= [_dest writeChar :*text];
        }
        else
        {
          ok &= writeTranslatedChar(_dest, *text);
        }
      }
    }
  }
  
  return ok;
}
      
//
// Write a comment
// 
// @param text     the text of the comment
// 
// @return success
// 
- (BOOL) comment :(const DXMLChar *) text
{
  BOOL ok = NO;
  
  if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else
  {
    ok = closeElement(self);
    
    if (text != NULL)
    {
      ok &= [_dest writeText :"<!--"];
      ok &= [_dest writeText :text];
      ok &= [_dest writeText :"-->"];
    }
  }
  
  return ok;
}

//
// Write a processing instruction
// 
// @param target   the target
// @param value    the value
// 
// @return success
// 
- (BOOL) processingInstruction :(const DXMLChar *) target :(const DXMLChar *) value
{
  BOOL ok = NO;
  
  if ((target == NULL) || (*target == EOS))
  {
    WARNING(DW_INVALID_ARG, "target");
  }
  else if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else
  {
    ok = closeElement(self);
    
    ok &= [_dest writeText :"<?"];
    ok &= [_dest writeText :target];
    
    if ((value != NULL) && (*value != EOS))
    {
      ok &= [_dest writeChar :' '];
      ok &= [_dest writeText :value];
    }
    
    ok &= [_dest writeText :"?>"];
  }
  
  return ok;
}
   
//
// Write the start of a CDATA section
// 
// @return success
// 
- (BOOL) startCDATA
{
  BOOL ok = NO;
  
  if (_dest == nil)
  {
    WARNING(DW_INVALID_ARG, "start");
  }
  else
  {
    ok = closeElement(self);
    
    ok &= [_dest writeText :"<![CDATA["];
    
    _inCData = YES;
  }
  
  return ok;
}

//
// Write the end of a CDATA section
// 
// @return success
// 
- (BOOL) endCDATA
{
  BOOL ok = NO;
  
  if (_dest == nil)
  {
    WARNING(DW_INVALID_ARG, "start");
  }
  else
  {
    ok = [_dest writeText :"]]>"];
    
    _inCData = NO;
  }
  
  return ok;
}

//
// Start of a namespace declaration
// 
// @param prefix   the prefix
// @param uri      the uri
// 
// @return success
// 
- (BOOL) startNamespace :(const DXMLChar *) prefix :(const DXMLChar *) uri
{
  DXMLNode *uNode = [DXMLNode alloc];
  DXMLNode *nNode = [DXMLNode alloc];
  
  [_unprocessed append :[uNode init :DXML_NAMESPACE :prefix :uri]];
  [_namespaces  push   :[nNode init :DXML_NAMESPACE :prefix :uri]];
  
  return YES;
}

//
// End of a namespace declaration
// 
// @return success
// 
- (BOOL) endNamespace
{
  DXMLNode *node = [_namespaces pop];
  
  if (node != nil)
  {
    [node free];
  }
  else
  {
    WARNING(DW_UNEXPECTED_ERROR, "missing namespace");
  }

  return YES;
}

//
// Write unparsed text in the xml document (e.g. the DTD)
// Note: there is no translation whatsoever done on this text.
// 
// @param text     the unparsed text
// 
// @return success
// 
- (BOOL) unparsed :(const DXMLChar *) text
{
  BOOL ok = NO;
  
  if (_dest == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "start");
  }
  else if (text != NULL)
  {
    ok = closeElement(self);
    
    ok = [_dest writeText :text];
  }
  
  return ok;
}

// 
// An error is found during the parsing of the file
// 
// @param number       the error number
// @param name         the name of the config file
// @param lineNumber   the line number in the config file
// @param columnNumber the column number in the config file
// 
// @return success
// 
- (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber;
{
  fprintf(stderr, "%s, line %d.%d: code:%d\n", name, lineNumber, columnNumber, number);
}
        
@end
#endif



  
  
#if _INTERFACE_

#ifdef HAVE_DXML

#include "ofc/DTree.h"

//
// The XML Tree class implements methods for reading, storing, changing,
// removing and writing xml information. Due to the fact that every xml
// piece of information is stored in a xml node, large xml files can take
// a lot of memory. Unparsed information is not stored in the tree. Use 
// a character that can't be part of an URI e.g. '|' for the namespace 
// separator.
//

@interface DXMLTree : DTree <DXMLHandler>
{
@private
  DTreeIterator *_iter;      // the iterator in the xml tree
  BOOL           _inCData;   // CDATA section entered ?
  char           _separator; // the namespace separator (or EOS for no namespaces)
}

#endif
#endif


#ifdef HAVE_DXML


@implementation DXMLTree


//// Constructors

//
// Initialise a config tree
//
// @return the object
//

- (DXMLTree *) init
{
  [super init];

  _iter = [DTreeIterator alloc]; [_iter init :self];
  
  _inCData = NO;

  _separator = EOS;
  
  return self;
}

//
// Initialise a xml tree with a xml source
// 
// @param source     the xml source
// @param filename   the name of the xml source
// @param separator  the namespace separator (or EOS for no namespaces)
//
// @return the object
//

- (DXMLTree *) init :(id <DTextReadable>) source :(const char *) name :(char) separator
{
  [self init];

  [self read :source :name :separator];
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object
//
// @return the object
//
- shallowCopy
{
  DXMLTree *copy = [super shallowCopy];
  
  copy->_iter = [DTreeIterator alloc]; [copy->_iter init :copy];
 
  return copy;
}


//// Deconstructor

//
// Free the object (including the stored objects)
//
// @return the object
//

- free
{
  [_iter free]; _iter = nil;
  
  return [super free];
}

//
// Free the object (without the stored objects)
//
// @return the object
//

- shallowFree
{
  [_iter free]; _iter = nil;
  
  return [super shallowFree];
}


//// Xml tree reading and writing

//
// Read the xml from a source
//  
// @param source   the source of the xml
// @param name     the name of the source
// @param separtor the namespace separator (or EOS for no namespaces)
// 
// @return success
// 

- (BOOL) read :(id <DTextReadable>) source :(const char *) name :(char) separator
{
  BOOL ok = NO;

  if ([self length] > 0)
  {
    WARNING(DW_UNEXPECTED_ERROR, "tree is not empty");
  }
  else
  {
    
    DXMLReader *reader = [DXMLReader new];
  
    ok = [reader parse :source :name :self :separator];
  
    [reader free];
    
    _separator = separator;
  }
    
  return ok;
}

//
// Write the xml to a destination
// 
// @param destination the destination of the xml
// @param name        the name of the destination (not used)
// 
// @return success
// 

- (BOOL) write :(id <DTextWritable>) destination :(const char *) name
{
  BOOL   ok   = NO;
  
  if (destination == nil)
  {
    WARNING(DW_INVALID_ARG, "destination");
  }
  else
  {
    DXMLWriter *writer = [DXMLWriter new];
    DXMLNode   *node   = nil;

    [writer start :destination :_separator];

    ok = YES;
    
    // iterate the xml tree
    node = [_iter root];
    while ((node != nil) && (ok))
    {
      if ([node isKindOf :[DXMLNode class]])
      {
        switch ([node type])
        {
        case DXML_ELEMENT   : ok &= [writer startElement :[node name]]; 
                              break;
        case DXML_ATTRIBUTE : ok &= [writer attribute :[node name] :[node value]]; 
                              break;
        case DXML_TEXT      : ok &= [writer characters :[node value]]; 
                              break;
        case DXML_CDATA     : ok &= [writer startCDATA]; 
                              ok &= [writer characters :[node value]]; 
                              ok &= [writer endCDATA];
                              break;
        case DXML_PI        : ok &= [writer processingInstruction :[node name] :[node value]];
                              break;
        case DXML_COMMENT   : ok &= [writer comment :[node value]];
                              break;
        case DXML_DOCUMENT  : ok &= [writer processingInstruction :"xml" :[node value]];
                              break;
        case DXML_NAMESPACE : ok &= [writer startNamespace :[node name] :[node value]];
                              break;
        }
      }
      
      if ([_iter hasChildren])
      {
        node = [_iter child];
      }
      else
      {
        do
        {
          if ([node isKindOf :[DXMLNode class]])
          {
            switch ([node type])
            {
              case DXML_ELEMENT   : ok &= [writer endElement];   break;
              case DXML_NAMESPACE : ok &= [writer endNamespace]; break;
            }
          }

          node = [_iter next];
          
          if (node != nil) break;

          node = [_iter parent];
        }
        while (node != nil);
      }
    }

    ok &= [writer endDocument];
      
    [writer free];
  }
  
  return ok;
}


//// Call back methods for the xml parser (private)

//
// Process the start of a document
// 
// @param version     the version number
// @param encoding    the encoding
// @param standalone  is the xml document standalone ? (-1 = missing)
// 
// @return success
// 
- (BOOL) startDocument :(const DXMLChar *) version :(const DXMLChar *) encoding :(int) standalone
{
  DXMLNode *node  = [DXMLNode alloc];
  DText    *value = [DText new];
  
  [_iter root];
  
  if (version != NULL)
  {
    [value writeText :" version=\""];
    [value writeText :version];
    [value writeChar :'"'];
  }
    
  if (encoding != NULL)
  {
    [value writeText :" encoding="];
    [value writeText :encoding];
    [value writeChar :'"'];
  }
  
  if (standalone != -1)
  {
    [value writeText :" standalone="];
    
    if (standalone)
    {
      [value writeText :"\"yes\""];
    }
    else
    {
      [value writeText :"\"no\""];
    }
  }
  
  [_iter append :[node init :DXML_DOCUMENT :NULL :[value cstring]]];
  
  [value free];
  
  return YES;
}

//
// Process the end of a document
// 
// @return success
// 
- (BOOL) endDocument
{
  // Not much to do..
  
  return YES;
}

//
// Process the start of an element
// 
// @param name     the name of the element
// 
// @return success
// 
- (BOOL) startElement :(const DXMLChar *) name
{
  DXMLNode *node = [DXMLNode alloc];

  [_iter append :[node init :DXML_ELEMENT :name :NULL]];
  
  return YES;
}

//
// Process an attribute for an element (after a startElement)
// 
// @param attribute  the attribute
// @param value      the value for the attribute
// 
// @return success
// 
- (BOOL) attribute :(const DXMLChar *) attribute :(const DXMLChar *) value
{
  DXMLNode *node = [DXMLNode alloc];

  [_iter append :[node init :DXML_ATTRIBUTE :attribute :value]];
  [_iter parent];
  
  return YES;
}

//
// Process the end of an element
// 
// @return success
//
- (BOOL) endElement
{
  [_iter parent];
  
  return YES;
}

//
// Process a string of characters
// 
// @param text     the text of the characters
// 
// @return success
// 
- (BOOL) characters :(const DXMLChar *) text
{
  DXMLNode *node = [DXMLNode alloc];
  
  if (_inCData)
  {
    [_iter append :[node init :DXML_CDATA :NULL :text]];
  }
  else
  {
    [_iter append :[node init :DXML_TEXT :NULL :text]];
  }
  [_iter parent];
  
  return YES;
}

//
// Process a comment
// 
// @param text     the text of the comment
// 
// @return success
// 
- (BOOL) comment :(const DXMLChar *) text
{
  DXMLNode *node = [DXMLNode alloc];
  
  [_iter append :[node init :DXML_COMMENT :NULL :text]];
  [_iter parent];
  
  return YES;
}

//
// Process a processing instruction
// 
// @param target   the target
// @param value    the value
// 
// @return success
// 
- (BOOL) processingInstruction :(const DXMLChar *) target :(const DXMLChar *) value
{
  DXMLNode *node = [DXMLNode alloc];
  
  [_iter append :[node init :DXML_PI :target :value]];
  [_iter parent];
  
  return YES;
}

//
// Process the start of a CDATA section
// 
// @return success
// 
- (BOOL) startCDATA
{
  _inCData = YES;
  
  return YES;
}

//
// Process the end of a CDATA section
// 
// @return success
// 
- (BOOL) endCDATA
{
  _inCData = NO;
  
  return YES;
}

//
// Start of a namespace declaration
// 
// @param prefix   the prefix
// @param uri      the uri
// 
// @return success
// 
- (BOOL) startNamespace :(const DXMLChar *) prefix :(const DXMLChar *) uri;
{
  DXMLNode *node = [DXMLNode alloc];
  
  [_iter append :[node init :DXML_NAMESPACE :prefix :uri]];
  
  return YES;
}

//
// End of a namespace declaration
// 
// @return success
// 
- (BOOL) endNamespace
{
  [_iter parent];
  
  return YES;
}

//
// Process unparsed text in the xml document (e.g. the DTD)
// 
// @param text     the unparsed text
// 
// @return success
// 
- (BOOL) unparsed :(const DXMLChar *) text
{
  // Skipping..

  return YES;
}

//
// An error is found during the parsing of the file
// 
// @param number       the error number
// @param name         the name of the config file
// @param lineNumber   the line number in the config file
// @param columnNumber the column number in the config file
// 
// @return success
// 

- (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber;
{
  fprintf(stderr, "%s, line %d.%d: code:%d\n", name, lineNumber, columnNumber, number);
}
        
@end
#endif

/*==========================================================================*/
